package terra.model.object;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import terra.Config;
import terra.database.DatabaseFactory;
import terra.managers.MapManager;
import terra.managers.ObjectManager;
import terra.model.World;
import terra.model.object.knownlist.ObjectKnownList;
import terra.model.object.map.AbstractMapObject;
import terra.model.object.map.MapObject;

/**
 * Класс статичных объектов, Прослушивают зависимые блоки на изменения любой тип
 * кроме NONE после изменения зависимого блока меняет свой тип на наземный, и
 * начинает падать.
 * */
public abstract class StaticObject extends GameObject {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	public static final int GROUND_TYPE = 0;
	public static final int LEFT_WALL_TYPE = 1;
	public static final int RIGHT_WALL_TYPE = 2;
	public static final int ROOF_TYPE = 3;
	public static final int NONE_TYPE = 4;
	boolean needUpdate = false;
	private int defId;
	/**
	 * координаты зависимого блока TODO их может несколько, в зависимости от
	 * размеров объекта
	 */
	private int groundTileX = -1;
	private int groundTileY = -1;
	/** тип объекта. По умолчанию является наземным. */
	private int type = GROUND_TYPE;

	public StaticObject() {
		super(World.getNextId());
	}
	
	public StaticObject(int objectId) {
		super(objectId);
	}

	public StaticObject(int objectId, int defId) {
		super(objectId);
		setDefId(defId);
	}
	
	public abstract void applyDef();
	
	@Override
	public ObjectKnownList getKnownList() {
		if (knownList == null)
			return knownList = new ObjectKnownList(this);
		return knownList;
	}

	@Override
	public void update(int delta) {
		// если тип статичного объекта нон - ничего не делать. он просто висит в
		// воздухе.
		needUpdate = false;
		if (getType() == NONE_TYPE) {
			//needUpdate = false;
			if (positionChanged()) {
				setOldX(getX());
				setOldY(getY());
			}
			return;
		}
		// если тип не пустой и не наземный - проверить, проходимый ли блок.
		// если да, то менять тип на наземный.
		AbstractMapObject tile = getTile();
		if (getType() == LEFT_WALL_TYPE || getType() == RIGHT_WALL_TYPE
				|| getType() == ROOF_TYPE) {
			if (tile == null) {
				setType(GROUND_TYPE);
			} else if (tile.canMove()) {
				// если крепимся к стене, и теперь меняем тип на наземный
				// то меняем прослушиваемый тайл на тот, в котором находится
				// объект.
				if (getType() == LEFT_WALL_TYPE)
					setTile(getGroundTileX() + 1, getGroundTileY(),
							getMapIndex());
				else if (getType() == RIGHT_WALL_TYPE)
					setTile(getGroundTileX() - 1, getGroundTileY(),
							getMapIndex());
				setType(GROUND_TYPE);
			}
			//needUpdate = true;
			// если тип наземный -
		} else if (getType() == GROUND_TYPE) {
			if (tile != null && tile.getType() == MapObject.TYPE_GROUND){
				tile.addListener(this);
				if (!intersects(tile.getX(), tile.getY(),
						Config.tileSize, Config.tileSize)){
					setYacceleration(getYacceleration()
							+ (Config.gravity / 1000) * delta);
					if (getYacceleration() > Config.maxItemGravity)
						setYacceleration(Config.maxItemGravity);
					setY(getY() + getYacceleration());
					needUpdate = true;
				} else {
					setYacceleration(0);
					needUpdate = false;
					if (positionChanged()) {
						setOldX(getX());
						setOldY(getY());
					}
				}
				
			} else if (tile == null || tile.getType() != MapObject.TYPE_GROUND
					|| tile.canMove()) {
				AbstractMapObject newGround;
				int newY = getGroundTileY() + 1;
				if (tile != null)
					newGround = tile.getNeighbor(MapObject.DOWN);
				else
					newGround = AbstractMapObject.getNeighbor(getGroundTileX(),
							getGroundTileY(), getMapIndex(), MapObject.DOWN);
				while (newGround == null
						|| (newGround.getType() != MapObject.TYPE_GROUND && newGround
								.canMove())) {
					if (newGround != null)
						newGround = newGround.getNeighbor(MapObject.DOWN);
					else
						newGround = AbstractMapObject.getNeighbor(
								getGroundTileX(), newY++, getMapIndex(),
								MapObject.DOWN);
					if (newY >= Config.mapHeight) {
						World.getInstance().removeObject(this);
						return;
					}
				}
				if (intersects(newGround.getX(), newGround.getY(),
						Config.tileSize, Config.tileSize)) {
					while (intersects(newGround.getX(), newGround.getY(),
							Config.tileSize, Config.tileSize)) {
						setY(getY() - 1);
					}
					if (tile != null)
						tile.removeListener(this);
					groundTileY = newGround.getTileY();
					getTile().addListener(this);
				} else {
					setYacceleration(getYacceleration()
							+ (Config.gravity / 1000) * delta);
					if (getYacceleration() > Config.maxItemGravity)
						setYacceleration(Config.maxItemGravity);
					setY(getY() + getYacceleration());
				}
				needUpdate = true;
			}
		}
		if (needUpdate)
			needUpdate();
	}

	public void objectChanged(GObject object) {
		if (object instanceof AbstractMapObject)
			needUpdate();
	}

	protected void removeObject() {
		if (getTile() != null)
			getTile().removeListener(this);
		if (this instanceof Door || this instanceof ItemObject)
			deleteStaticObject();
	}

	public int getGroundTileX() {
		return groundTileX;
	}

	public void setGroundTileX(int groundTileX) {
		this.groundTileX = groundTileX;
	}

	public int getGroundTileY() {
		return groundTileY;
	}

	public void setGroundTileY(int groundTileY) {
		this.groundTileY = groundTileY;
	}

	public AbstractMapObject getTile() {
		try{
		return MapManager.instance.getMaps().get(getMapIndex()).getMap()[groundTileX][groundTileY];
		} catch(Exception e){
			e.printStackTrace();
			System.out.println("mapIndex "+ getMapIndex());
			System.out.println("map "+ MapManager.instance.getMaps().get(getMapIndex()).getMap());
		}
		return null;
	}

	public int getType() {
		return type;
	}

	public void setType(int type) {
		this.type = type;
	}

	public void setTile(AbstractMapObject tile) {
		if (tile == null)
			return;
		if (groundTileX != -1 && groundTileY != -1)
			if (getTile() != null)
				getTile().removeListener(this);
		setGroundTileY(tile.getTileY());
		setGroundTileX(tile.getTileX());
	}

	public void setTile(int x, int y, int mapIndex) {
		if (groundTileX != -1 && groundTileY != -1)
			if (getTile() != null)
				getTile().removeListener(this);
		setGroundTileY(y);
		if (x >= 0 && x < Config.mapWidth) {
			setGroundTileX(x);
		} else {
			if (x < 0) {
				setGroundTileX(x + Config.mapWidth);
			}
			if (x > Config.mapWidth) {
				setGroundTileX(x - Config.mapWidth);
			}

		}
	}

	public void deleteStaticObject(){
		try (Connection con = DatabaseFactory.getInstance().getConnection();
				PreparedStatement statement = con
						.prepareStatement("DELETE FROM staticobjects WHERE objectId=?")) {
			statement.setInt(1, getObjectId());
			statement.execute();
			statement.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public void saveStaticObject(){
		try (Connection con = DatabaseFactory.getInstance().getConnection();
				PreparedStatement statement = con
						.prepareStatement("UPDATE staticobjects SET npcId=?,x=?,y=?,count=?,ground_x=?,ground_y=? WHERE objectId=?")) {
			statement.setInt(1, getDefId());
			statement.setInt(2, (int) getX());
			statement.setInt(3, (int) getY());
			statement.setInt(4, this instanceof ItemObject ? ((ItemObject)this).getItemCount() : 1);
			statement.setInt(5, getGroundTileX());
			statement.setInt(6, getGroundTileY());
			statement.setInt(7, getObjectId());
			statement.execute();
			statement.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static StaticObject loadStaticObject(ResultSet rs) throws SQLException{
		StaticObject object = ObjectManager.createObject(rs.getInt("objectId"),rs.getInt("npcId"), rs.getString("instance"));
		object.setX(rs.getInt("x"));
		object.setY(rs.getInt("y"));
		object.setGroundTileX(rs.getInt("ground_x"));
		object.setGroundTileY(rs.getInt("ground_y"));
		object.applyDef();
		return object;
	}

	public void insertStaticObject() {
		try (Connection con = DatabaseFactory.getInstance().getConnection();
				PreparedStatement statement = con
						.prepareStatement("INSERT INTO staticobjects (objectId,npcId,x,y,count,instance,respawn_time,spawn_x,spawn_y,ground_x,ground_y) VALUES (?,?,?,?,?,?,?,?,?,?,?)");) {
			statement.setInt(1, getObjectId());
			statement.setInt(2, getDefId());			
			statement.setInt(3, (int) getX());
			statement.setInt(4, (int) getY());
			statement.setInt(5, this instanceof ItemObject ? ((ItemObject)this).getItemCount() : 1);
			statement.setString(6, this.getClass().getSimpleName());
			//FIXME respawn time for flora
			int respawnTime = 60000;
			if (this instanceof ItemObject)
				respawnTime = -1;
			if (this instanceof Door)
				respawnTime = -1;
			statement.setInt(7,  respawnTime);
			statement.setInt(8, (int) getX());
			statement.setInt(9, (int) getY());
			statement.setInt(10, getGroundTileX());
			statement.setInt(11, getGroundTileY());

			statement.execute();
			statement.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public int getDefId() {
		return defId;
	}

	public void setDefId(int defId) {
		this.defId = defId;
	}
	


}
